﻿#region 참조 네임스페이스

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

#endregion

namespace Skying.ComponentModel
{
	/// <summary>
	/// 정렬 가능한 데이터 바인딩을 지원하는 제네릭 컬렉션을 제공합니다.
	/// </summary>
	/// <typeparam name="T">목록에 있는 요소의 형식을 나타낸다.</typeparam>
	public class SortableBindingList<T> : BindingList<T>
	{
		#region 변수

		/// <summary>
		/// 데이터 정렬 여부를 나타낸다.
		/// </summary>
		private bool _isSorted;

		/// <summary>
		/// 목록이 정렬되는 방향을 나타낸다.
		/// </summary>
		private ListSortDirection _sortDirection;

		/// <summary>
		/// 목록의 정렬에 사용되는 속성 설명자를 나타낸다.
		/// </summary>
		private PropertyDescriptor _sortProperty;

		#endregion

		#region 프로퍼티

		/// <summary>
		/// 목록이 정렬되는 방향을 가져온다.
		/// </summary>
		/// <remarks>
		/// <para><see cref="System.ComponentModel.BindingList<T>.SortDirectionCore"/>을 재정의한다.</para>
		/// </remarks>
		protected override ListSortDirection SortDirectionCore
		{
			get
			{
				return this._sortDirection;
			}
		}

		/// <summary>
		/// 정렬 기능이 파생 클래스에서 구현되었으면 목록의 정렬에 사용되는 속성 설명자를 가져오고, 그렇지 않으면 null을 반환한다.
		/// </summary>
		/// <remarks>
		/// <para><see cref="System.ComponentModel.BindingList<T>.SortPropertyCore"/>을 재정의한다.</para>
		/// </remarks>
		protected override PropertyDescriptor SortPropertyCore
		{
			get
			{
				return _sortProperty;
			}
		}

		/// <summary>
		/// 목록이 정렬되어 있는지 여부를 나타내는 값을 가져온다.
		/// </summary>
		/// <remarks>
		/// <para><see cref="System.ComponentModel.BindingList<T>.IsSortedCore"/>을 재정의한다.</para>
		/// </remarks>
		protected override bool IsSortedCore
		{
			get
			{
				return _isSorted;
			}
		}

		/// <summary>
		/// 목록에서 정렬 기능을 지원하는지 여부를 나타내는 값을 가져온다.
		/// </summary>
		/// <remarks>
		/// <para><see cref="System.ComponentModel.BindingList<T>.SupportsSortingCore"/>을 재정의한다.</para>
		/// </remarks>
		protected override bool SupportsSortingCore
		{
			get
			{
				return true;
			} 
		}

		#endregion

		#region 생성자/소멸자

		public SortableBindingList()
		{

		}

		public SortableBindingList(List<T> list)
			: base(list)
		{

		}

		#endregion

		#region 정렬

		/// <summary>
		/// 정렬 기능이 파생 클래스에서 구현되었으면 <see cref="System.ComponentModel.BindingList<T>.RemoveSortCore()"/>를 사용하여 적용된 정렬을 제거하고, 그렇지 않으면 <see cref="System.NotSupportedException"/>을 발생시킵니다. 
		/// </summary>
		protected override void RemoveSortCore()
		{
			_isSorted = false;
		}

		/// <summary>
		/// 파생 클래스에서 재정의되면 항목을 정렬하고, 그렇지 않으면 <see cref="System.NotSupportedException"/>을 throw합니다. 
		/// </summary>
		/// <param name="prop">정렬할 속성을 설정한다.</param>
		/// <param name="direction">목록을 정렬할 방향을 설정한다.</param>
		protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
		{
			if (prop.PropertyType.GetInterface("IComparable") == null) return;

			List<T> items = this.Items as List<T>;

			if (items == null)
			{
				this._isSorted = false;
			}
			else
			{
				var comparer = new SortPropertyComparer(prop.Name, direction);

				items.Sort(comparer);

				this._isSorted = true;

				this._sortDirection = direction;

				this._sortProperty = prop;
			}

			base.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
		}

		#endregion

		public class SortPropertyComparer : IComparer<T>
		{
			private PropertyInfo PropInfo
			{
				get;
				set;
			}

			private ListSortDirection Direction
			{
				get;
				set;
			}

			#region 생성자/소멸자

			public SortPropertyComparer(string propName, ListSortDirection direction)
			{
				this.PropInfo = typeof(T).GetProperty(propName);

				this.Direction = direction;
			}

			#endregion

			#region 비교

			public int Compare(T x, T y)
			{
				object xValue = PropInfo.GetValue(x, null);
				object yValue = PropInfo.GetValue(y, null);

				if (Direction == ListSortDirection.Ascending)
				{
					return Comparer.Default.Compare(xValue, yValue);
				}
				else
				{
					return Comparer.Default.Compare(yValue, xValue);
				}
			}

			#endregion
		}
	}
}